# LeetCode 23、合并K个升序链表(归并思路)

# 一、题目描述

给你一个链表数组,每个链表都已经按升序排列。

请你将所有链表合并到一个升序链表中,返回合并后的链表。

示例 1:

输入:lists = [[1,4,5],[1,3,4],[2,6]]
输出:[1,1,2,3,4,4,5,6]
解释:链表数组如下:
[
  1->4->5,
  1->3->4,
  2->6
]
将它们合并到一个有序链表中得到。
1->1->2->3->4->4->5->6

提示:

  • k == lists.length
  • 0 <= k <= 10^4
  • 0 <= lists[i].length <= 500
  • -10^4 <= lists[i][j] <= 10^4
  • lists[i]升序 排列
  • lists[i].length 的总和不超过 10^4

# 二、题目解析

# 三、参考代码

# 1、Java 代码

// 登录 AlgoMooc 官网获取更多算法图解
// https://www.algomooc.com
// 作者:程序员吴师兄
// 代码有看不懂的地方一定要私聊咨询吴师兄呀
// 合并K个升序链表:https://leetcode-cn.com/problems/merge-k-sorted-lists/
class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        
        // 如果划分的 lists 总的链表个数为 0 
        if(lists.length == 0){
            // 返回空
            return null;
        }

        // 如果划分的 lists 总的链表个数为 1
        if(lists.length == 1){
            // 不需要再划分,返回这个链表
            return lists[0];
        }

        // 如果划分的 lists 总的链表个数为 2
        if(lists.length == 2){
            // 将这两个链表合并
            return mergeTwoLists(lists[0],lists[1]);
        }

        // 计算中点
        int mid = lists.length / 2;

        // 设置两个存放链表的数组
        // sub1 用来存放前部分的那些链表
        ListNode[] sub1 = new ListNode[mid];
        for(int i = 0 ; i < mid ; i++){
            sub1[i] = lists[i];
        }

        // sub2 用来存放后部分的那些链表
        ListNode[] sub2 = new ListNode[lists.length - mid ];
        for(int i = mid ; i < lists.length ; i++){
            sub2[i - mid] = lists[i];
        }
        
        // 获取 sub1 合并之后的结果
        ListNode l1 = mergeKLists(sub1);

        // 获取 sub2 合并之后的结果
        ListNode l2 = mergeKLists(sub2);

        // 将这两个链表合并返回
        return mergeTwoLists(l1,l2);
    }

    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        // 一开始设置一个虚拟节点,它的值为 -1,它的值可以设置为任何的数,因为我们根本不需要使用它的值
        ListNode dummy = new ListNode(-1);

        // 设置一个指针,指向虚拟节点
        ListNode pre = dummy;

        // 通过一个循环,不断的比较 l1 和 l2 中当前节点值的大小,直到 l1 或者 l2 遍历完毕为止
        while (l1 != null && l2 != null) {
            // 如果 l1 当前节点的值小于等于了 l2 当前节点的值
            if (l1.val <= l2.val) {
                // 让 pre 指向节点的 next 指针指向这个更小值的节点
                // 即指向 l1
                pre.next = l1;
                // 让 l1 向后移动
                l1 = l1.next;
            }else {
                // 让 pre 指向节点的 next 指针指向这个更小值的节点
                // 即指向 l2
                pre.next =l2;
                // 让 l2 向后移动
                l2 = l2.next;
            }
            // 让 pre 向后移动
            pre = pre.next;
        }

        // 跳出循环后,l1 或者 l2 中可能有剩余的节点没有被观察过
        // 直接把剩下的节点加入到 pre 的 next 指针位置

        // 如果 l1 中还有节点
        if (l1 != null) {
            // 把 l1 中剩下的节点全部加入到 pre 的 next 指针位置
            pre.next = l1;
        }

        // 如果 l2 中还有节点
        if (l2 != null) {
            // 把 l2 中剩下的节点全部加入到 pre 的 next 指针位置
            pre.next = l2;
        }

        // 最后返回虚拟节点的 next 指针
        return dummy.next;
    }
}

# **2、**C++ 代码

// 登录 AlgoMooc 官网获取更多算法图解
// https://www.algomooc.com
// 作者:程序员吴师兄
// 代码有看不懂的地方一定要私聊咨询吴师兄呀
// 合并K个升序链表:https://leetcode-cn.com/problems/merge-k-sorted-lists/
class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        // 如果划分的 lists 总的链表个数为 0 
        if(lists.size() == 0){
            // 返回空
            return NULL;
        }

        // 如果划分的 lists 总的链表个数为 1
        if(lists.size() == 1){
            // 不需要再划分,返回这个链表
            return lists[0];
        }

        // 如果划分的 lists 总的链表个数为 2
        if(lists.size() == 2){
            // 将这两个链表合并
            return mergeTwoLists(lists[0],lists[1]);
        }

        return merge(lists , 0 , lists.size() - 1);
    }
private:
    ListNode* merge(vector<ListNode*>& lists,int left,int right){
        if( left > right ){
            return NULL;
        }
        if( left == right ){
            return lists[left];
        }
        int mid = ( left + right )/2;
        return mergeTwoLists(merge(lists,left,mid),merge(lists,mid+1,right));
    }
private:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        // 一开始设置一个虚拟节点,它的值为 -1,它的值可以设置为任何的数,因为我们根本不需要使用它的值
        ListNode *dummy = new ListNode(-1);

        // 设置一个指针,指向虚拟节点
        ListNode *pre = dummy;

        // 通过一个循环,不断的比较 l1 和 l2 中当前节点值的大小,直到 l1 或者 l2 遍历完毕为止
        while (l1 != NULL && l2 != NULL) {
            // 如果 l1 当前节点的值小于等于了 l2 当前节点的值
            if (l1->val <= l2->val) {
                // 让 pre 指向节点的 next 指针指向这个更小值的节点
                // 即指向 l1
                pre->next = l1;
                // 让 l1 向后移动
                l1 = l1->next;
            }else {
                // 让 pre 指向节点的 next 指针指向这个更小值的节点
                // 即指向 l2
                pre->next =l2;
                // 让 l2 向后移动
                l2 = l2->next;
            }
            // 让 pre 向后移动
            pre = pre->next;
        }

        // 跳出循环后,l1 或者 l2 中可能有剩余的节点没有被观察过
        // 直接把剩下的节点加入到 pre 的 next 指针位置

        // 如果 l1 中还有节点
        if (l1 != NULL) {
            // 把 l1 中剩下的节点全部加入到 pre 的 next 指针位置
            pre->next = l1;
        }

        // 如果 l2 中还有节点
        if (l2 != NULL) {
            // 把 l2 中剩下的节点全部加入到 pre 的 next 指针位置
            pre->next = l2;
        }

        // 最后返回虚拟节点的 next 指针
        return dummy->next;
    }
};

# 3、Python 代码

# 登录 AlgoMooc 官网获取更多算法图解
# https://www.algomooc.com
# 作者:程序员吴师兄
# 代码有看不懂的地方一定要私聊咨询吴师兄呀
# 合并K个升序链表:https://leetcode-cn.com/problems/merge-k-sorted-lists/
class Solution:
    def mergeKLists(self, lists: List[ListNode]) -> ListNode:
        if not lists:return 
        return self.merge(lists,0,len(lists) - 1)
    
    def merge(self,lists,left , right ):
        
        if left == right : 
            return lists[left]
        
        # 计算中点
        mid = left + (right - left) // 2

        # 获取 l1 合并之后的结果
        l1 = self.merge(lists, left, mid)

        # 获取 l2 合并之后的结果
        l2 = self.merge(lists, mid+1, right)

        # 将这两个链表合并返回
        return self.mergeTwoLists(l1,l2)

    def mergeTwoLists(self, l1, l2):

        # 一开始设置一个虚拟节点,它的值为 -1,它的值可以设置为任何的数,因为我们根本不需要使用它的值
        dummy = ListNode(-1)

        # 设置一个指针,指向虚拟节点
        pre = dummy

        # 通过一个循环,不断的比较 l1 和 l2 中当前节点值的大小,直到 l1 或者 l2 遍历完毕为止
        while l1 and l2:
            # 如果 l1 当前节点的值小于等于了 l2 当前节点的值
            if l1.val <= l2.val :
                # 让 pre 指向节点的 next 指针指向这个更小值的节点
                # 即指向 l1
                pre.next = l1
                # 让 l1 向后移动
                l1 = l1.next
            else:
                # 让 pre 指向节点的 next 指针指向这个更小值的节点
                # 即指向 l2
                pre.next =l2
                # 让 l2 向后移动
                l2 = l2.next

            # 让 pre 向后移动
            pre = pre.next


        # 跳出循环后,l1 或者 l2 中可能有剩余的节点没有被观察过
        # 直接把剩下的节点加入到 pre 的 next 指针位置

        # 如果 l1 中还有节点
        if l1 != None :
            # 把 l1 中剩下的节点全部加入到 pre 的 next 指针位置
            pre.next = l1


        # 如果 l2 中还有节点
        if l2 != None :
            # 把 l2 中剩下的节点全部加入到 pre 的 next 指针位置
            pre.next = l2;


        # 最后返回虚拟节点的 next 指针
        return dummy.next

# 四、复杂度分析

时间复杂度:O(kn×logk)。

空间复杂度:递归会使用到 O(logk) 空间代价的栈空间。